package com.ruoyi.exam.serive.impl;

import cn.hutool.core.collection.ListUtil;
import cn.hutool.core.util.ArrayUtil;
import com.ruoyi.common.core.domain.entity.SysUser;
import com.ruoyi.common.vo.PageResult;
import com.ruoyi.exam.domain.ExamInfo;
import com.ruoyi.exam.domain.ExamRankingExt;
import com.ruoyi.exam.domain.ExamScores;
import com.ruoyi.exam.manager.ExamScoreManager;
import com.ruoyi.exam.mapper.ExamRankingExtMapper;
import com.ruoyi.exam.mapper.ExamScoresMapper;
import com.ruoyi.exam.serive.IExamScoreService;
import com.ruoyi.exam.serive.IExamService;
import com.ruoyi.exam.vo.ExamRankingData;
import com.ruoyi.exam.vo.ExamRankingReqVO;
import com.ruoyi.exam.vo.ExamScoreListReqVO;
import com.ruoyi.exam.vo.ExamScoreSubmitVO;
import com.ruoyi.student.IStudentUserService;
import com.ruoyi.student.vo.UserInfoVO;
import com.ruoyi.system.service.ISysUserService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.User;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;

@Slf4j
@Service
public class ExamScoreServiceImpl implements IExamScoreService {
    @Autowired
    private ExamScoresMapper scoresMapper;
    @Autowired
    private ExamScoreManager scoreManager;
    @Autowired
    private ExamRankingExtMapper rankingExtMapper;

    @Autowired
    private IExamService examService;

    @Autowired
    private IStudentUserService userService;

    @Override
    public ExamScores get(long uid, int examId) {
        return scoresMapper.getExamScores(examId, uid);
    }

    @Override
    @Transactional
    public void submitHandsOnScore(ExamScoreSubmitVO scoreVO) {
        ExamScores scores = scoresMapper.getExamScores(scoreVO.getExamId(), scoreVO.getUid());
        List<Runnable> rockBackCache = new ArrayList<>();
        try {
            if (scores == null) {
                scores = ExamScores.buildEmpty();
                scores.setExamId(scoreVO.getScores());
                scores.setUid(scoreVO.getUid());
                scores.setHandsOnScore(scoreVO.getScores());
                scores.setHandsOnTime(scoreVO.getTime());

                rockBackCache.add(scoreManager.addRanking(scoreVO.getExamId(), scoreVO.getUid(), scoreVO.getScores(),
                        ExamScoreManager.ExamRankingKey.HAND_ON_SCORE));
                rockBackCache.add(scoreManager.addRanking(scoreVO.getExamId(), scoreVO.getUid(), scoreVO.getTime(),
                        ExamScoreManager.ExamRankingKey.HAND_ON_TIME));
            } else {
                if (scores.getHandsOnScore() == null || scores.getHandsOnScore() == -1) {
                    scores.setHandsOnScore(scoreVO.getScores());
                    scores.setHandsOnTime(scoreVO.getTime());

                    rockBackCache.add(scoreManager.addRanking(scoreVO.getExamId(), scoreVO.getUid(), scoreVO.getScores(),
                            ExamScoreManager.ExamRankingKey.HAND_ON_SCORE));
                    rockBackCache.add(scoreManager.addRanking(scoreVO.getExamId(), scoreVO.getUid(), scoreVO.getTime(),
                            ExamScoreManager.ExamRankingKey.HAND_ON_TIME));
                    if (scores.getTheoryScore() != null && scores.getTheoryScore() != -1) {
                        scores.setScores(totalScore(scores.getHandsOnScore(), scores.getTheoryScore()));
                        scores.setMultipleTime(scores.getHandsOnTime() + scores.getTheoryTime());

                        rockBackCache.add(scoreManager.addRanking(scoreVO.getExamId(), scoreVO.getUid(), scores.getScores(),
                                ExamScoreManager.ExamRankingKey.SCORES));
                        rockBackCache.add(scoreManager.addRanking(scoreVO.getExamId(), scoreVO.getUid(), scores.getMultipleTime(),
                                ExamScoreManager.ExamRankingKey.MULTIPLE_TIME));
                    }
                    scoresMapper.updateExamScores(scores);
                } else {
                    log.error("Repeated submission of hands-On score[" + scoreVO + "]");
                }
            }
        } catch (Throwable t) {
            rockBackCache.forEach(runnable -> runnable.run());
            throw t;
        }
    }

    private int totalScore(int handsOnScore, int theoryScore) {
        return (int) Math.ceil((double) handsOnScore / 2 + (double) theoryScore / 2);
    }

    @Override
    @Transactional
    public void submitTheoryScore(ExamScoreSubmitVO scoreVO) {
        ExamScores scores = scoresMapper.getExamScoresForUpdate(scoreVO.getExamId(), scoreVO.getUid());
        List<Runnable> rockBackCache = new ArrayList<>();
        try {
            if (scores == null) {
                scores = ExamScores.buildEmpty();
                scores.setExamId(scoreVO.getExamId());
                scores.setUid(scoreVO.getUid());
                scores.setTheoryScore(scoreVO.getScores());
                scores.setTheoryTime(scoreVO.getTime());

                rockBackCache.add(scoreManager.addRanking(scoreVO.getExamId(), scoreVO.getUid(), scores.getTheoryScore(),
                        ExamScoreManager.ExamRankingKey.THEORY_SCORE));
                rockBackCache.add(scoreManager.addRanking(scoreVO.getExamId(), scoreVO.getUid(), scores.getTheoryTime(),
                        ExamScoreManager.ExamRankingKey.THEORY_TIME));

                scoresMapper.addExamScores(scores);
            } else {
                if (scores.getTheoryScore() == null || scores.getTheoryScore() == -1) {
                    scores.setTheoryScore(scoreVO.getScores());
                    scores.setTheoryTime(scoreVO.getTime());

                    rockBackCache.add(scoreManager.addRanking(scoreVO.getExamId(), scoreVO.getUid(), scores.getTheoryScore(),
                            ExamScoreManager.ExamRankingKey.THEORY_SCORE));
                    rockBackCache.add(scoreManager.addRanking(scoreVO.getExamId(), scoreVO.getUid(), scores.getTheoryTime(),
                            ExamScoreManager.ExamRankingKey.THEORY_TIME));

                    if (scores.getHandsOnScore() != null && scores.getHandsOnScore() != -1) {
                        scores.setScores(totalScore(scores.getHandsOnScore(), scores.getTheoryScore()));
                        scores.setMultipleTime(scores.getHandsOnTime() + scores.getTheoryTime());

                        rockBackCache.add(scoreManager.addRanking(scoreVO.getExamId(), scoreVO.getUid(), scores.getScores(),
                                ExamScoreManager.ExamRankingKey.SCORES));
                        rockBackCache.add(scoreManager.addRanking(scoreVO.getExamId(), scoreVO.getUid(), scores.getMultipleTime(),
                                ExamScoreManager.ExamRankingKey.MULTIPLE_TIME));
                    }
                    scoresMapper.updateExamScores(scores);
                } else {
                    log.error("Repeated submission of theory score[" + scoreVO + "]");
                }
            }
        } catch (Throwable t) {
            rockBackCache.forEach(runnable -> runnable.run());
            throw t;
        }
    }

    @Override
    public List<ExamRankingData> listRankingData(ExamRankingReqVO rankingReqVo) {
        ExamInfo examInfo = examService.getLastExam();
        if (examInfo == null) {
            return new ArrayList<>();
        }
        int tmpId = examInfo.getId();
        if (!rankingExtMapper.isExistData(tmpId)) {
            tmpId = scoresMapper.getPrevExamId(tmpId);
        }
        int examId = tmpId;
        int start = rankingReqVo.getStartIndex();
        int end = start + rankingReqVo.getPageSize();
        ExamScoreManager.ExamRankingKey rankingKey = ExamScoreManager.ExamRankingKey.parse(rankingReqVo.getRankingKey());
        List<ExamScoreManager.ExamRanking> list = scoreManager.getRankRange(examId, start, end, rankingKey);
        if (list != null && !list.isEmpty()) {
            List<Long> uids = new ArrayList<>(list.size());
            List<ExamRankingData> dataList = list.stream().map(ranking -> {
                uids.add(ranking.getUid());
                ExamRankingData data = new ExamRankingData();
                data.setExamId(examId);
                data.setUid(ranking.getUid());
                data.setRank(ranking.getRank());
                data.setScore(ranking.getScore());
                return data;
            }).collect(Collectors.toList());

            List<UserInfoVO> users = userService.getUserInfo(uids);
            Map<Long, UserInfoVO> map = users.stream().collect(Collectors.toMap(UserInfoVO::getUid, u -> u));
            dataList.forEach(data -> {
                data.setNickname(map.get(data.getUid()).getNickname());
                data.setCollege(map.get(data.getUid()).getDeptName());
                data.setSubjectName(getSubjectName(rankingReqVo.getRankingKey()));
            });

            return dataList;
        }
        return new ArrayList<>(0);
    }

    private String getSubjectName(String rankingKey) {
        if (rankingKey.startsWith("HAND_ON")) {
            return "实操";
        } else if (rankingKey.startsWith("THEORY")) {
            return "理论";
        } else {
            return "综合";
        }
    }

    @Override
    public PageResult<ExamScores> pageScores(ExamScoreListReqVO reqVO) {
        int size = scoresMapper.sizeListScore(reqVO);
        PageResult<ExamScores> pageResult = new PageResult<>(reqVO.getPageNo(), reqVO.getPageSize(), size);
        if (size > 0) {
            List<ExamScores> list = scoresMapper.listScore(reqVO);
            list.forEach(scores -> {
                if (scores.getScoresRanking() == null) {
                    int r = scoreManager.getRank(scores.getExamId(), scores.getUid(), ExamScoreManager.ExamRankingKey.SCORES);
                    scores.setScoresRanking(r);
                }
                if (scores.getHandsOnScoreRanking() == null) {
                    int r = scoreManager.getRank(scores.getExamId(), scores.getUid(), ExamScoreManager.ExamRankingKey.HAND_ON_SCORE);
                    scores.setHandsOnScoreRanking(r);
                }
                if (scores.getTheoryScoreRanking() == null) {
                    int r = scoreManager.getRank(scores.getExamId(), scores.getUid(), ExamScoreManager.ExamRankingKey.THEORY_SCORE);
                    scores.setTheoryScoreRanking(r);
                }
                if (scores.getMultipleTimeRanking() == null) {
                    int r = scoreManager.getRank(scores.getExamId(), scores.getUid(), ExamScoreManager.ExamRankingKey.MULTIPLE_TIME);
                    scores.setMultipleTimeRanking(r);
                }
                if (scores.getHandsOnTimeRanking() == null) {
                    int r = scoreManager.getRank(scores.getExamId(), scores.getUid(), ExamScoreManager.ExamRankingKey.HAND_ON_TIME);
                    scores.setHandsOnTimeRanking(r);
                }
                if (scores.getTheoryTimeRanking() == null) {
                    int r = scoreManager.getRank(scores.getExamId(), scores.getUid(), ExamScoreManager.ExamRankingKey.THEORY_TIME);
                    scores.setTheoryTimeRanking(r);
                }
            });
            pageResult.addAll(list);
        }
        return pageResult;
    }

//    @Transactional
//    public void onExamEnd(int examId) {
//        doUpdateExamRankingList(examId, ExamScoreManager.ExamRankingKey.SCORES);
//        doUpdateExamRankingList(examId, ExamScoreManager.ExamRankingKey.HAND_ON_SCORE);
//        doUpdateExamRankingList(examId, ExamScoreManager.ExamRankingKey.THEORY_SCORE);
//        doUpdateExamRankingList(examId, ExamScoreManager.ExamRankingKey.MULTIPLE_TIME);
//        doUpdateExamRankingList(examId, ExamScoreManager.ExamRankingKey.HAND_ON_TIME);
//        doUpdateExamRankingList(examId, ExamScoreManager.ExamRankingKey.THEORY_TIME);
//
//        if (rankingExtMapper.countExistForLock(examId) == 0) {
//            ExamRankingExt ext = new ExamRankingExt();
//            ext.setExamId(examId);
//            ext.setExistData(true);
//            ext.setPersistence(true);
//            rankingExtMapper.addExamRankingExt(ext);
//        } else {
//            rankingExtMapper.setPersistence(examId);
//        }
//    }

    /**
     * 考试结束后操作
     *  更新排名信息
     * @param examId
     */
    @Transactional
    @Override
    public void onExamEnd(int examId) {
        List<ExamScores> list = getAllExamScores(examId);
        doUpdateExamRankingList(list, ExamScoreManager.ExamRankingKey.SCORES);
        doUpdateExamRankingList(list, ExamScoreManager.ExamRankingKey.HAND_ON_SCORE);
        doUpdateExamRankingList(list, ExamScoreManager.ExamRankingKey.THEORY_SCORE);
        doUpdateExamRankingList(list, ExamScoreManager.ExamRankingKey.MULTIPLE_TIME);
        doUpdateExamRankingList(list, ExamScoreManager.ExamRankingKey.HAND_ON_TIME);
        doUpdateExamRankingList(list, ExamScoreManager.ExamRankingKey.THEORY_TIME);

        if (rankingExtMapper.countExistForLock(examId) == 0) {
            ExamRankingExt ext = new ExamRankingExt();
            ext.setExamId(examId);
            ext.setExistData(true);
            ext.setPersistence(true);
            rankingExtMapper.addExamRankingExt(ext);
        } else {
            rankingExtMapper.setPersistence(examId);
        }
    }


    private void doUpdateExamRankingList(List<ExamScores> list, ExamScoreManager.ExamRankingKey ranking) {
        sortExamRankingList(list, ranking);
        UpdateRanking update = switchUpdateRanking(ranking);
        int start = 0;
        int step = 500;
        int len = (int) Math.ceil(list.size() / (double)step);
        List<ExamScores> tmp;
        for(int i = 0 ; i < len ; i++){
            tmp = ListUtil.sub(list ,start , start + step);
            update.update(tmp);
            start += step;
        }
    }


    private void sortExamRankingList(List<ExamScores> list, ExamScoreManager.ExamRankingKey ranking) {
        AtomicInteger idx = new AtomicInteger(1);
        Set<Integer> existScore = new HashSet<>(list.size());
        int curr;
        ExamScores pre = null;
        switch (ranking) {
            case SCORES:
                Collections.sort(list, (e1, e2) -> e2.getScores() - e1.getScores());
                for (ExamScores scores : list) {
                    curr = idx.getAndIncrement();
                    if (!existScore.add(scores.getScores())) {
                        curr = pre.getScoresRanking();
                    }
                    scores.setScoresRanking(curr);
                    pre = scores;
                }
                break;
            case HAND_ON_SCORE:
                Collections.sort(list, (e1, e2) -> e2.getHandsOnScore() - e1.getHandsOnScore());
                for (ExamScores scores : list) {
                    curr = idx.getAndIncrement();
                    if (!existScore.add(scores.getHandsOnScore())) {
                        curr = pre.getHandsOnScoreRanking();
                    }
                    scores.setHandsOnScoreRanking(curr);
                    pre = scores;
                }
                break;
            case THEORY_SCORE:
                Collections.sort(list, (e1, e2) -> e2.getTheoryScore() - e1.getTheoryScore());
                for (ExamScores scores : list) {
                    curr = idx.getAndIncrement();
                    if (!existScore.add(scores.getTheoryScore())) {
                        curr = pre.getTheoryScoreRanking();
                    }
                    scores.setTheoryScoreRanking(curr);
                    pre = scores;
                }
                break;
            case MULTIPLE_TIME:
                list.stream()
                        .filter(s -> s.getMultipleTime() == null || s.getMultipleTime() <= 0)
                        .forEach(s -> s.setMultipleTime(Integer.MAX_VALUE));
                Collections.sort(list, Comparator.comparingInt(ExamScores::getMultipleTime));
                for (ExamScores scores : list) {
                    curr = idx.getAndIncrement();
                    if (!existScore.add(scores.getMultipleTime())) {
                        curr = pre.getMultipleTimeRanking();
                    }
                    scores.setMultipleTimeRanking(curr);
                    pre = scores;
                }
                break;
            case HAND_ON_TIME:
                list.stream()
                        .filter(s -> s.getHandsOnTime() == null || s.getHandsOnTime() <= 0)
                        .forEach(s -> s.setHandsOnTime(Integer.MAX_VALUE));
                Collections.sort(list, Comparator.comparingInt(ExamScores::getHandsOnTime));
                for (ExamScores scores : list) {
                    curr = idx.getAndIncrement();
                    if (!existScore.add(scores.getHandsOnTime())) {
                        curr = pre.getHandsOnTimeRanking();
                    }
                    scores.setHandsOnTimeRanking(curr);
                    pre = scores;
                }
                break;
            case THEORY_TIME:
                list.stream()
                        .filter(s -> s.getTheoryTime() == null || s.getTheoryTime() <= 0)
                        .forEach(s -> s.setTheoryTime(Integer.MAX_VALUE));
                Collections.sort(list, Comparator.comparingInt(ExamScores::getTheoryTime));
                for (ExamScores scores : list) {
                    curr = idx.getAndIncrement();
                    if (!existScore.add(scores.getTheoryTime())) {
                        curr = pre.getTheoryTimeRanking();
                    }
                    scores.setTheoryTimeRanking(curr);
                    pre = scores;
                }
                break;
        }

    }

    private List<ExamScores> getAllExamScores(int examId) {
        int pageSize = 500;
        ExamRankingReqVO reqVO = new ExamRankingReqVO();
        reqVO.setExamId(examId);
        reqVO.setPageSize(pageSize);
        List<ExamScores> tmp;
        List<ExamScores> all = new ArrayList<>();
        do {
            tmp = scoresMapper.pageScore(reqVO);
            all.addAll(tmp);
            if (tmp.size() < pageSize) {
                break;
            }
        } while (true);
        return all;
    }


//    private void doUpdateExamRankingList(int examId, ExamScoreManager.ExamRankingKey ranking) {
//        int start = 0;
//        int count = 500;
//        List<ExamScoreManager.ExamRanking> list;
//        List<ExamScores> examScoresList;
//        boolean hasNext = false;
//        do {
//            list = scoreManager.getRankRange(examId, start, start + count, ranking);
//            if (list != null && !list.isEmpty()) {
//                if (list.size() == count) {
//                    start += count;
//                    hasNext = true;
//
//                    UpdateRanking update = switchUpdateRanking(ranking);
//                    examScoresList = convertToExamScores(list, ranking);
//                    update.update(examScoresList);
//                }
//            }
//        } while (hasNext);
//    }

//    private List<ExamScores> convertToExamScores(List<ExamScoreManager.ExamRanking> list, ExamScoreManager.ExamRankingKey ranking) {
//        return list.stream().map(s -> {
//            ExamScores scores = new ExamScores();
//            scores.setUid(s.getUid());
//            switch (ranking) {
//                case SCORES:
//                    scores.setScoresRanking(s.getRank());
//                    break;
//                case HAND_ON_SCORE:
//                    scores.setHandsOnScoreRanking(s.getRank());
//                    break;
//                case THEORY_SCORE:
//                    scores.setTheoryScoreRanking(s.getRank());
//                    break;
//                case MULTIPLE_TIME:
//                    scores.setMultipleTimeRanking(s.getRank());
//                    break;
//                case HAND_ON_TIME:
//                    scores.setHandsOnTimeRanking(s.getRank());
//                    break;
//                case THEORY_TIME:
//                    scores.setTheoryTimeRanking(s.getRank());
//                    break;
//            }
//            return scores;
//        }).collect(Collectors.toList());
//    }

    private UpdateRanking switchUpdateRanking(ExamScoreManager.ExamRankingKey ranking) {
        switch (ranking) {
            case SCORES:
                return list -> scoresMapper.updateScoresRanking(list);
            case HAND_ON_SCORE:
                return list -> scoresMapper.updateHandsOnScoreRanking(list);
            case THEORY_SCORE:
                return list -> scoresMapper.updateTheoryScoreRanking(list);
            case MULTIPLE_TIME:
                return list -> scoresMapper.updateMultipleTimeRanking(list);
            case HAND_ON_TIME:
                return list -> scoresMapper.updateHandsOnTimeRanking(list);
            case THEORY_TIME:
                return list -> scoresMapper.updateTheoryTimeRanking(list);
            default:
                throw new IllegalArgumentException("exam ranking key undefined[" + ranking + "]");
        }
    }

    private interface UpdateRanking {
        void update(List<ExamScores> list);
    }
}
